Cloudflare TurnstileをPythonで試してみた
こんにちは。CX事業本部Delivery部のakkyです。
先日、2022年9月28日にCloudflare Turnstileというサービスが発表されました。 https://blog.cloudflare.com/turnstile-private-captcha-alternative/
Turnstile(=回転式ゲート、改札口の意)はCAPTCHAの代替サービスです。歪んだ文字を読んだり、画像から指定された物体が写っているタイルを選択したりすることなく、ユーザーがbotではなく人間であることを検証できます。基本的にはユーザーは何も操作する必要はなく、操作の必要がある場合でも、それはチェックを付けるワンクリックだけで済みます。また、Cookieを読まず、プライバシーに配慮しているとされています。
Turnstileは現在はオープンベータ版として提供されていますが、すでに利用できるようになっていますので、実際に試してみました。
Cloudflareでの登録
CloudflareのWebコンソールから、Turnstile(Beta)を選択し、Add siteをクリックすると、以下のような登録画面が表示されます。
SitenameとDomainを入力し、Widgetを選びます。WidgetはManagedがデフォルトですが、これは後からでも変更できます。 Domainだけは最初から入力する必要があるので、サイトは事前に作成しておいてください。
これだけで作成完了です。フロントエンドで使うSite keyとバックエンドで使うSecret keyが表示されます。この値は後からでも表示できます。
Python+Flaskでの実装
バックエンドは例によってPython+Flask構成で、Zappaを使ってLambda+API Gatewayにデプロイします。 今回はメッセージフォームを保護することを想定して作ってみました。
まずはPython側のコードです。
from flask import Flask, render_template, request import requests app = Flask(__name__) TURNSTILE_SECRET = "0xXXXXXXXXXXXXXXXXXXXXXXX" @app.get("/") def form(): return render_template("form.html") @app.post("/validate") def validate(): name = request.form['name'] message = request.form['message'] turnstile = request.form.get('cf-turnstile-response') if not turnstile: return "トークンがありません" url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify' payload = {'secret': TURNSTILE_SECRET, 'response': turnstile, "remoteip": request.remote_addr} r = requests.post(url, data=payload) j = r.json() if j["success"] == True: return f"検証成功 name:{name} message:{message}<br>{r.content}" else: return f"検証失敗<br>{r.content}"
TURNSTILE_SECRETにSecret keyを入力してください。Turnstileでは、送信フォームにcf-turnstile-responseという値で検証キーが送られてきますので、Secret keyとユーザーのIPアドレス(オプション)と一緒に検証用のエンドポイントにPOSTすると、検証の成功、失敗が取得できるという構成になっています。 ペイロードはフォーム形式で送れば良いようです。
メッセージフォームは以下のようにしました。
<!DOCTYPE html> <html lang="ja"> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/[email protected]/mvp.css"> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> <script> function turnstileVerified(token) { console.log(token); document.getElementById("sendbutton").disabled = null; } </script> <title>メッセージフォーム</title> </head> <body> <main> <h1>メッセージフォーム</h1> <form action="{{ url_for('validate') }}" method="POST"> <label for="name">お名前</label> <input type="text" name="name"> <label for="message">メッセージ</label> <textarea name="message" rows="4" cols="40"></textarea> <div class="cf-turnstile" data-sitekey="0xXXXXXXXXXXXXXX" data-callback="turnstileVerified"></div> <button type="submit" value="Submit" id="sendbutton" disabled>送信</button> </form> </main> </body> </html>
data-sitekeyにSite keyを入力して下さい。Turnstileでは、検証完了時のコールバックが設定できるので、検証終了後にボタンを有効化するようにしてみました。
動作確認
フォームにアクセスすると、Turnstileの検証が自動的に始まります。
検証が完了するとSuccessと表示され、ボタンが有効化されます。ユーザーは実際に何もする必要がありません!
検証にも成功しますね。
では、botの場合はきちんとはじかれるのでしょうか? Seleniumでページを表示してみました。
おっと、チェックボックスが表示されてしまいましたね。これをチェックすると・・・
Failure!になりました。botだと見破ることができましたね。この状態で無理やり送信しても、検証が失敗することも確認できました。 ちなみに、WidgetをNon-interactiveにすると、チェックボックスは表示されず、いきなりFailureになります。
また、Invisibleにすると、何も表示されず、送信ボタンは有効になりませんでした。ただし、Invisibleでは人間ユーザーであっても検証前に送信するとトークンが送信されず、わけもわからず検証が失敗してしまうので、通常はManagedかNon-interactiveを使えば良いかと思います。
おわりに
Cloudflareの新サービスであるTurnstileを使ってみました。HTMLにわずかなコードを追加し、バックエンド側でもHTTPアクセスするだけで簡単に検証ができます。ユーザー側にわずらわしさを感じさせることのないCAPTCHAシステムなので、気軽に使えそうです。Cloudflareのブログにもあるように、アクセシビリティにも良さそうです。タイルから画像を選択するCAPTCHAも結構面倒なんですよね、私は微妙に隣のタイルにかかっている信号機を選択すればいいのか、いつも悩んでしまいます。
このようにTurnstileは簡単に使えることが確認できたのですが、まだ問題もあります。現在、オープンベータ版なので仕方がないのですが、ページをリロードしたり、また単に新規アクセスした場合でもウィジットが表示されず、検証できない場面に何度も遭遇しました。Explicitly renderを使用してもこの事象は発生しました。このような不具合はしばらくすれば改善されると思いますが、まだ実運用には注意が必要です。